package com.fengwk.support.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.DiffCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;

import com.fengwk.support.util.ArrayUtils;
import com.fengwk.support.util.CollectionUtils;

public class JGitUtils {

	public final static String GIT_DIR = ".git";
	
	private final static String DEFAULT_REMARK_COMMIT = "commit";

	/**
	 * 克隆
	 * 
	 * @param url
	 * @param local
	 * @throws InvalidRemoteException
	 * @throws TransportException
	 * @throws GitAPIException
	 */
	public static Git clone(String url, File local, String username, String password) throws InvalidRemoteException, TransportException, GitAPIException {
		CloneCommand cc = Git.cloneRepository().setURI(url).setDirectory(local);
		if (username != null && password != null)
			cc.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password));
		return cc.call();
	}
	
	/**
	 * 扫描目录下的git文件
	 * 
	 * @param f
	 * @return
	 */
	public static File scanGitDir(File f) {
		if (!f.isDirectory()) 
			return null;
		File[] fs = f.listFiles();
		File gitDir = null;
		if (ArrayUtils.isNotBlank(fs)) {
			for (File cf: fs) {
				if (cf.exists() && cf.isDirectory() && GIT_DIR.equals(cf.getName())) {
					gitDir = cf;
					break;
				}
			}
		}
		return gitDir;
	}
	
	/**
	 * 打开或初始化一个git仓库
	 * 
	 * @param path 仓库路径
	 * @param isInit true:当git仓库不存在时会初始化一个仓库
	 * @return
	 * @throws IllegalStateException
	 * @throws GitAPIException
	 * @throws IOException
	 */
	public static Git open(File f, boolean isInit) throws IllegalStateException, GitAPIException, IOException {
		Git git = null;
		if (isInit) {
			// 检查是否需要初始化仓库
			if (scanGitDir(f) == null)
				git = Git.init().setDirectory(f).call();
		}
		if (git == null)
			git = Git.open(f);
		return git;
	}
	
	/**
	 * 关闭
	 * 
	 * @param git
	 */
	public static void close(Git git) {
		git.close();
	}
	
	/**
	 * 提交,自动检测目录更变
	 * 
	 * @param git 
	 * @param filePaths 需要提交的文件路径
	 * @param remark 提交标记
	 * @param name 提交者姓名
	 * @param email 提交者邮箱
	 * @return 返回本次提交的版本号
	 * @throws NoFilepatternException
	 * @throws GitAPIException
	 */
	public static String commit(Git git, Collection<String> filePaths, String remark, String name, String email) throws NoFilepatternException, GitAPIException {
		// 判断工作区与暂存区的文件内容是否有变更
		DiffCommand dc = git.diff();
		if (filePaths != null)
			dc.setPathFilter(PathFilterGroup.createFromStrings(filePaths));
		List<DiffEntry> diffEntries = dc.setShowNameAndStatusOnly(true).call();
		if (CollectionUtils.isBlank(diffEntries)) {
			return null;
		}
		// 被修改过的文件
		List<String> updateFiles = new ArrayList<String>();
		ChangeType changeType;
		for (DiffEntry entry : diffEntries) {
			changeType = entry.getChangeType();
			switch (changeType) {
			case ADD:
			case COPY:
			case RENAME:
			case MODIFY:
				updateFiles.add(entry.getNewPath());
				break;
			case DELETE:
				updateFiles.add(entry.getOldPath());
				break;
			}
		}
		// 将文件提交到git仓库中，并返回本次提交的版本号
		// 1、将工作区的内容更新到暂存区
		AddCommand addCmd = git.add();
		for (String file : updateFiles)
			addCmd.addFilepattern(file);
		addCmd.call();
		// 2、commit
		RevCommit revCommit = commit(git, updateFiles, remark, name, email);
		return revCommit.getName();
	}
	
	// 提交
	private static RevCommit commit(Git git, List<String> files, String remark, String name, String email) throws NoHeadException, NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException, AbortedByHookException, GitAPIException {
		CommitCommand commitCmd = git.commit();
		for (String file : files)
			commitCmd.setOnly(file);
		if (name != null && email != null)
			commitCmd.setCommitter(name, email);
		if (remark == null)
			remark = DEFAULT_REMARK_COMMIT;
		return commitCmd.setMessage(remark).call();
	}
	
	/**
	 * 推送
	 * 
	 * @param git
	 * @param remote
	 * @param username
	 * @param password
	 * @return
	 * @throws InvalidRemoteException
	 * @throws TransportException
	 * @throws GitAPIException
	 */
	public static Iterable<PushResult> push(Git git, String remote, String username, String password) throws InvalidRemoteException, TransportException, GitAPIException {
		return git.push().setRemote(remote).setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password)).call();
	}

}
